using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace Framework
{
    /// <summary>
    /// Image manipulation
    /// </summary>
    /// <remarks>
    /// (GOOD) http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-saving-cropping-and-resizing
    /// http://www.codeproject.com/KB/GDI-plus/imgresizoutperfgdiplus.aspx
    /// http://www.telerik.com/help/aspnet-ajax/upload_howtoresizingimages.html
    /// http://snippets.dzone.com/posts/show/4336
    /// http://www.webcosmoforums.com/asp/321-create-high-quality-thumbnail-resize-image-dynamically-asp-net-c-code.html
    /// http://blog.nineon.com/chintan/post/Create-High-Quality-Thumbnails-Resize-using-System-Drawing-Graphics-ASP-Net-C-Code.aspx
    /// http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-saving-cropping-and-resizing
    /// http://forums.asp.net/t/29645.aspx
    /// http://www.c-sharpcorner.com/UploadFile/jacquesphilip/MakingThumbnailsOfTransparentImages11162005004933AM/MakingThumbnailsOfTransparentImages.aspx
    /// http://www.techtalkz.com/c-c-sharp/123638-resize-image-keeping-aspect-ratio.html
    /// </remarks>
    public static partial class ImgUtil
	{
        static ImgUtil()
        {
            ImageScale = new ImageScaleHelper();
        }

        public static ImageScaleHelper ImageScale { get; set; }

        #region LoadFromFile
        /// <summary>
        /// Loads an image from file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static Image LoadFromFile(string fileName)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName");
            }

            using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                return Image.FromStream(stream);
                // Image.FromFile(fileName) // Locks the file for some reason
            }
        }
        #endregion

        #region LoadFromBase64String
        /// <summary>
        /// Loads an image from string.
        /// </summary>
        /// <param name="src">Source of the image.</param>
        /// <returns></returns>
        public static Image LoadFromBase64String(string src)
        {
            var imageBytes = Convert.FromBase64String(src);
            
            var ms = new MemoryStream(imageBytes, 0, imageBytes.Length);
            
            ms.Write(imageBytes, 0, imageBytes.Length);

            return Image.FromStream(ms, true);
        }
        #endregion

        #region Resize
        /// <summary>
        /// Resizes the specified image. Maintains the aspect ratio by default
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <returns></returns>
        public static Image Resize(Image image, int width, int height)
        {
            return Resize(image, true, new Size(width, height));
        }

        /// <summary>
        /// Resizes the specified image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="maintainAspectRatio">if set to <c>true</c> [maintains the aspect ratio].</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <returns></returns>
        public static Image Resize(Image image, bool maintainAspectRatio, int width, int height)
        {
            return Resize(image, maintainAspectRatio, new Size(width, height));
        }

        /// <summary>
        /// Resizes the specified image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="maintainAspectRatio">if set to <c>true</c> [maintains the aspect ratio].</param>
        /// <param name="size">The size.</param>
        /// <returns></returns>
        public static Image Resize(Image image, bool maintainAspectRatio, Size size)
        {
            var targetSize = ResizeAsSize(image.Width, image.Height, maintainAspectRatio, size.Width, size.Height);

            var result = new Bitmap(targetSize.Width, targetSize.Height);

            using (var graphic = Graphics.FromImage(result))
            {
                graphic.CompositingQuality = CompositingQuality.HighQuality;
                graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphic.SmoothingMode = SmoothingMode.HighQuality;

                graphic.DrawImage(image, 0, 0, targetSize.Width, targetSize.Height);
            }

            return result;
        }
        #endregion

        public static Size ResizeAsSize(int sourceWidth, int sourceHeight, bool maintainAspectRatio, int targetWidth, int targetHeight)
        {
            if (maintainAspectRatio)
            {
                if (targetWidth == 0 && targetHeight == 0)
                {
                    targetWidth = sourceWidth;
                    targetHeight = sourceHeight;
                }
                else
                {
                    var ratioX = ((float) targetWidth/sourceWidth);
                    var ratioY = ((float) targetHeight/sourceHeight);

                    //var ratio = Math.Abs(ratioX - 0.0f) < float.Epsilon || (ratioY > 0 && ratioY < ratioX) ? ratioY : ratioX;

                    var ratio = (ratioY > 0 && ratioY < ratioX) || ratioX <= 0 ? ratioY : ratioX;
                    //var nPercent = sourceWidth == 0 || (sourceHeight > 0 && ratioY < ratioX) ? ratioY : ratioX;

                    // New width and height based on aspect ratio
                    targetWidth = (int) (sourceWidth*ratio);
                    targetHeight = (int) (sourceHeight*ratio);
                }
            }

            return new Size(targetWidth, targetHeight);
        }

        #region Scale
        public static Image ScaleMaxVertical(Image image, int maxHeight)
        {
            return ScaleToMax(image, maxHeight, ImageScaleDirectionType.Vertical);
        }

        public static Image ScaleMaxHorizontal(Image image, int maxWidth)
        {
            return ScaleToMax(image, maxWidth, ImageScaleDirectionType.Horizontal);
        }

        private static Image ScaleToMax(Image image, int max, ImageScaleDirectionType scaleDirection)
        {
            var ratio = ImageScale.GetRatio(image, max, scaleDirection);

            var destWidth = (int)(image.Width * ratio);
            var destHeight = (int)(image.Height * ratio);

            var result = new Bitmap(destWidth, destHeight);

            using (var graphic = Graphics.FromImage(result))
            {
                graphic.CompositingQuality = CompositingQuality.HighQuality;
                graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphic.SmoothingMode = SmoothingMode.HighQuality;

                graphic.DrawImage(image, 0, 0, destWidth, destHeight);
            }

            return result;
        }
        #endregion

        #region Crop
        /// <summary>
        /// Crops the specified image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <returns></returns>
        public static Image Crop(Image image, int x, int y, int width, int height)
        {
            var cropArea = new Rectangle(x, y, width, height);

            return Crop(image, cropArea);
        }

        /// <summary>
        /// Crops the specified image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="cropArea">The crop area.</param>
        /// <returns></returns>
        public static Image Crop(Image image, Rectangle cropArea)
        {
            var result = new Bitmap(image);

            return result.Clone(cropArea, result.PixelFormat);
        } 
        #endregion

        #region Save
        /// <summary>
        /// Saves the supplied image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="targetFile">The target file.</param>
        /// <param name="imageFormat">The image format.</param>
        public static void Save(Image image, string targetFile, ImageFormat imageFormat)
        {
            image.Save(targetFile, imageFormat);
        }
        #endregion

        #region SaveJpeg
        /// <summary>
        /// Saves the supplied image as a High quality JPEG.
        /// </summary>
        /// <param name="sourceImage">The source image.</param>
        /// <param name="targetFile">The target file.</param>
        public static void SaveJpeg(Image sourceImage, string targetFile)
        {
            SaveJpeg(sourceImage, targetFile, 100);
        }

	    /// <summary>
        /// Saves the supplied image as a JPEG.
        /// </summary>
        /// <remarks>
        /// Quality is from '1 - 100' where 1 is low quality and 100 is high quality
        /// </remarks>
        /// <param name="sourceImage">The source image.</param>
        /// <param name="targetFile">The target file.</param>
        /// <param name="quality">The quality.</param>
        public static void SaveJpeg(Image sourceImage, string targetFile, long quality)
        {
            // Jpeg image codec
	        var codec = GetEncoderInfoJpeg();

            if (codec == null)
            {
                return;
            }

            // Encoder parameter for image quality
            var encoderParams = GetEncoderParams_JpegImageQuality(quality);

            sourceImage.Save(targetFile, codec, encoderParams);
        } 
        #endregion

        // *********************************************************************************
        // Encoder Info (Gif, Jpeg) etc...
        // *********************************************************************************

        #region GetEncoderInfo
        /// <summary>
        /// Gets the encoder info for the supplied mime type.
        /// </summary>
        /// <param name="mimeType">Type of the MIME.</param>
        /// <returns></returns>
        public static ImageCodecInfo GetEncoderInfo(string mimeType)
        {
            return ImageCodecInfo.GetImageEncoders().FirstOrDefault(t => t.MimeType == mimeType);
        }
        public static ImageCodecInfo GetEncoderInfo(ImageFormat format)
        {
            return ImageCodecInfo.GetImageDecoders().SingleOrDefault(c => c.FormatID == format.Guid);
        }
        #endregion

        #region GetEncoderInfoJpeg
        /// <summary>
        /// Gets the encoder info for JPEG type.
        /// </summary>
        /// <returns></returns>
        public static ImageCodecInfo GetEncoderInfoJpeg()
        {
            return GetEncoderInfo("image/jpeg");
        }
        #endregion

        #region GetEncoderInfoGif
        /// <summary>
        /// Gets the encoder info for GIF type.
        /// </summary>
        /// <returns></returns>
        public static ImageCodecInfo GetEncoderInfoGif()
        {
            return GetEncoderInfo("image/gif");
        }
        #endregion

        #region GetEncoderInfoPng
        /// <summary>
        /// Gets the encoder info for PNG type.
        /// </summary>
        /// <returns></returns>
        public static ImageCodecInfo GetEncoderInfoPng()
        {
            return GetEncoderInfo("image/png");
        }
        #endregion

        // *********************************************************************************
        // Encoder Parameters
        // *********************************************************************************

        #region GetEncoderParams_JpegImageQuality
        /// <summary>
        /// Gets the encoder parameters image quality.
        /// </summary>
        /// <remarks>
        /// Quality is from '1 - 100' where 1 is low quality and 100 is high quality
        /// </remarks>
        /// <param name="quality">The quality.</param>
        /// <returns></returns>
        public static EncoderParameters GetEncoderParams_JpegImageQuality(long quality)
        {
            if (quality <0 || quality>100)
            {
                throw new Exception("JPEG image quality must be tetween 0 and 100.");
            }

            var result = new EncoderParameters(1);

            var qualityParam = new EncoderParameter(Encoder.Quality, quality);

            result.Param[0] = qualityParam;

            return result;
        }
        #endregion

        // *********************************************************************************
        // Inner helper classes 
        // *********************************************************************************
        public class ImageScaleHelper
        {
            public float GetRatio(Image image, int maxSize, ImageScaleDirectionType direction)
            {
                return GetRatio(image.Width, image.Height, maxSize, direction);
            }

            public float GetRatio(int imageWidth, int imageHeight, int maxSize, ImageScaleDirectionType direction)
            {
                if (direction == ImageScaleDirectionType.None)
                {
                    return 1.0f;
                }
                return (float)maxSize / (direction == ImageScaleDirectionType.Horizontal ? imageWidth : imageHeight);
            }
        }
    }
}

